Skip to content

Feature: Auto-update client#4256

Closed
mohamed-essam wants to merge 37 commits intomainfrom
feat/auto-upgrade
Closed

Feature: Auto-update client#4256
mohamed-essam wants to merge 37 commits intomainfrom
feat/auto-upgrade

Conversation

@mohamed-essam
Copy link
Copy Markdown
Contributor

@mohamed-essam mohamed-essam commented Jul 30, 2025

Describe your changes

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • Extended the README / documentation, if necessary
  • Documentation is not needed

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Summary by CodeRabbit

  • New Features

    • Configurable auto-update: clients can auto-update to latest, a specific version, or be disabled; initial auto-update applied at startup.
    • Update progress UI: displays live progress during client upgrades.
    • Platform update support: macOS, Windows, Linux (and placeholders for other targets).
  • Server & Sync

    • Management surfaces auto-update settings in sync payloads and account APIs; emits events when account auto-update version changes.
  • Tests

    • Added coverage for update manager and version decision logic
  • Documentation

    • auto_update_version exposed in account settings API

✏️ Tip: You can customize this high-level summary in your review settings.

@mohamed-essam mohamed-essam force-pushed the feat/auto-upgrade branch 4 times, most recently from 76b9397 to 9d44077 Compare July 30, 2025 18:08
Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread client/internal/updatemanager/update_windows.go Outdated
Comment thread client/internal/updatemanager/update_windows.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
@robertgro
Copy link
Copy Markdown
Contributor

Related

#1793

#4019

@mohamed-essam
Copy link
Copy Markdown
Contributor Author

@lixmal All comments should be resolved, will need to conduct another round of testing however, so will keep the PR as Draft until testing is complete

@mohamed-essam mohamed-essam force-pushed the feat/auto-upgrade branch 5 times, most recently from 03eb50e to d15b3e5 Compare August 4, 2025 14:50
@mohamed-essam mohamed-essam marked this pull request as ready for review August 4, 2025 14:51
@mlsmaycon mlsmaycon requested a review from Copilot August 5, 2025 12:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces an auto-update client feature that allows the management server to control client version updates. The feature enables administrators to specify a target version for clients, which can be "latest", "disabled", or a specific semantic version.

  • Added AutoUpdateVersion field to account settings with validation for "latest", "disabled", or semantic version format
  • Implemented platform-specific update mechanisms for Windows, macOS, Linux, and FreeBSD
  • Added update manager that monitors version changes and triggers updates when client version is older than target

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
version/update.go Made LatestAvailable field public for external access
management/server/types/settings.go Added AutoUpdateVersion field to Settings struct
management/server/token_mgr.go Added AutoUpdateVersion to TURN/relay token updates
management/server/peer.go Added AutoUpdateVersion to peer deletion updates
management/server/http/handlers/accounts/ Added API handlers and validation for AutoUpdateVersion
management/server/http/api/ Added AutoUpdateVersion to API types and OpenAPI spec
management/server/grpcserver.go Added AutoUpdateVersion to sync response
management/server/activity/codes.go Added activity tracking for AutoUpdateVersion updates
management/server/account.go Added handling for AutoUpdateVersion setting changes
management/proto/management.proto Added autoUpdateVersion field to SyncResponse
client/internal/updatemanager/ Implemented platform-specific update mechanisms
client/internal/engine.go Integrated update manager with sync response handling

Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread management/server/http/handlers/accounts/accounts_handler.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go
Comment thread client/internal/updatemanager/update_windows.go Outdated
pascal-fischer
pascal-fischer previously approved these changes Aug 8, 2025
Copy link
Copy Markdown
Collaborator

@lixmal lixmal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you put any thoughts into how we could do a rollback if the new version breaks connectivity?

Comment thread client/internal/updatemanager/update_darwin.go Outdated
Comment thread client/internal/updatemanager/update_darwin.go Outdated
@mohamed-essam
Copy link
Copy Markdown
Contributor Author

mohamed-essam commented Aug 13, 2025

@lixmal

Have you put any thoughts into how we could do a rollback if the new version breaks connectivity?

Based on my discussions with @mlsmaycon , the upgrade mechanism is forward-moving only

@mohamed-essam
Copy link
Copy Markdown
Contributor Author

@lixmal If it's alright with you, I will mark the PR as no documentation needed for the time being as the dashboard changes are not yet done, as soon as dashboard can modify the auto upgrade version I will open a PR in docs

Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go Outdated
Comment thread client/internal/updatemanager/manager.go
@BashBandito
Copy link
Copy Markdown

Very much looking forward to this feature.

Thanks for the good work.

Comment thread client/internal/updatemanager/manager.go Outdated
u.cancel()
if u.update != nil {
u.update.StopWatch()
u.update = nil
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You set u.update = nil in Stop() while updateLoop or handleUpdate could still try to access it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only referenced inside handleUpdate once inside a mutex, so I used this to prevent a race and also added a check so that if u.update is nil it should return

return false
}

if time.Since(u.lastTrigger) < 5*time.Minute {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure but we can miss a potential upgrade if we just ignore. What is the goal with this condition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so it doesn't spam retrying to upgrade with any trigger since each failure will show an error to the user, so this is just a way to backoff failures

Comment thread client/internal/updatemanager/manager.go Outdated
@nazarewk
Copy link
Copy Markdown
Contributor

fixes #4019 #1793 #832

@ash-nb ash-nb mentioned this pull request Nov 5, 2025
7 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 12, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds an automatic update subsystem and integrates it end-to-end: new UpdateManager with platform triggers, wiring into Engine startup and sync flows, management API/proto/OpenAPI changes to carry auto-update settings, UI progress support, and tests plus version-fetcher refactor.

Changes

Cohort / File(s) Summary
Update Manager Core
client/internal/updatemanager/manager.go
New UpdateManager, UpdateInterface, UpdateState; lifecycle (Start, Stop, CheckUpdateSuccess, SetVersion), update loop, state persistence, decision logic, throttling, and event publishing.
Update Manager Tests
client/internal/updatemanager/manager_test.go
New tests, test helpers (WithCustomVersionUpdate), mock UpdateInterface, scenarios for latest/specific version handling and assertions.
Platform Update Implementations
client/internal/updatemanager/update_darwin.go, client/internal/updatemanager/update_windows.go, client/internal/updatemanager/update_linux.go, client/internal/updatemanager/update_freebsd.go, client/internal/updatemanager/update_js.go
Platform-specific triggerUpdate implementations: Darwin (pkg/Homebrew installer), Windows (EXE/MSI via registry/installer), Linux/FreeBSD/JS provide build-tagged stubs/TODOs.
Engine Integration
client/internal/connect.go, client/internal/engine.go
Invoke Engine.InitialUpdateHandling after engine start when login PeerConfig contains auto-update; add Engine.updateManager field, InitialUpdateHandling and handleAutoUpdateVersion; stop updateManager on Engine.Stop.
Version Fetcher Refactor
version/update.go, version/update_test.go
Introduce NewUpdateAndStart and StartFetcher, add LatestVersion accessor, nil-safety guards; tests updated to use NewUpdateAndStart.
UI Update Progress
client/ui/client_ui.go
Add --show-update flag, update progress window and lifecycle, initialize version fetcher with NewUpdateAndStart.
Management Server: API / Events / Types
management/server/account.go, management/server/activity/codes.go, management/server/http/handlers/accounts/accounts_handler.go, management/server/http/handlers/accounts/accounts_handler_test.go, management/server/types/settings.go
Add Settings.AutoUpdateVersion and API exposure/validation ("latest"/"disabled"/semver), emit AccountAutoUpdateVersionUpdated event on change, update tests.
GRPC / Network Mapping
management/server/grpcserver.go, management/server/peer.go, shared/management/proto/management.proto
Add AutoUpdateSettings proto and embed autoUpdate in PeerConfig; toPeerConfig/toSyncResponse and deletePeers now include PeerConfig with auto-update.
OpenAPI / Generated Types
shared/management/http/api/openapi.yml, shared/management/http/api/types.gen.go
Document and expose auto_update_version in AccountSettings; generated API types add AutoUpdateVersion.
Management Init Change
management/internals/server/server.go
Swap NewUpdate → NewUpdateAndStart for server-side update watcher initialization.
Manifests
go.mod
Module manifest updates for new dependencies/targets referenced by changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Engine
    participant Management
    participant UpdateMgr as UpdateManager
    participant Platform
    participant State

    User->>Engine: Start
    Engine->>Management: Login / Sync request
    Management-->>Engine: SyncResponse (NetworkMap with PeerConfig.autoUpdate)
    alt peerConfig.autoUpdate present
        Engine->>Engine: InitialUpdateHandling(autoUpdateSettings)
        Engine->>UpdateMgr: Start / SetVersion(autoUpdate.version)
        UpdateMgr->>State: Load persisted state
        UpdateMgr->>UpdateMgr: evaluate shouldUpdate()
        alt shouldUpdate == true
            UpdateMgr->>Platform: triggerUpdate(targetVersion)
            Platform-->>UpdateMgr: install result
            UpdateMgr->>State: persist update outcome
        end
    end
    Engine-->>User: Running
Loading
sequenceDiagram
    participant NetworkSync
    participant Engine
    participant UpdateMgr

    NetworkSync->>Engine: handleSync(networkMap with PeerConfig)
    alt networkMap.PeerConfig present
        Engine->>Engine: handleAutoUpdateVersion(autoUpdateSettings, initialCheck=false)
        Engine->>UpdateMgr: SetVersion / maybe Start
        UpdateMgr->>UpdateMgr: updateLoop or immediate handleUpdate
    end
    Engine-->>NetworkSync: Sync processed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Review focus:
    • client/internal/updatemanager/manager.go (concurrency, persistence, time-throttling, error handling)
    • client/internal/engine.go and client/internal/connect.go (startup ordering and initial handling)
    • Platform installers: update_darwin.go, update_windows.go (security, process invocation, restart logic)
    • version/update.go (lifecycle changes and race conditions)
    • Proto/OpenAPI changes for compatibility: shared/management/proto/management.proto, shared/management/http/api/openapi.yml

Possibly related PRs

Suggested reviewers

  • lixmal

Poem

🐰 I hopped through code with eager paws,

New updates follow server laws.
Manager wakes and checks the state,
Downloads, swaps, and seals the fate.
A joyful hop — the client awakes!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description largely fails to provide meaningful information: 'Describe your changes' and 'Issue ticket number and link' are completely empty, and 'Stack' is missing. Only the checklist and CLA confirmation are filled. Fill in the 'Describe your changes' section with a summary of the auto-update implementation; add the issue ticket number and link; and complete the 'Stack' section if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature: Auto-update client' clearly and concisely summarizes the main change—adding an auto-update feature to the client.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auto-upgrade

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f1a714 and bdf22d0.

📒 Files selected for processing (1)
  • management/server/types/settings.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • management/server/types/settings.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
version/update.go (1)

175-179: Guard against latestAvailable being nil.

On the very first successful fetch, u.latestAvailable is still nil, so calling .Equal dereferences nil and panics, killing the watcher. Add a nil check before comparing.

-	if u.latestAvailable.Equal(latestAvailable) {
+	if u.latestAvailable != nil && u.latestAvailable.Equal(latestAvailable) {
 		return false
 	}
🧹 Nitpick comments (5)
shared/management/proto/management.proto (1)

274-281: Tighten semantics in AutoUpdateSettings comments (values, policy timing, presence)

Add explicit value contract for version and clarify update policy timing. Suggested comment-only change:

 message AutoUpdateSettings {
-  string version = 1;
-  /*
-    alwaysUpdate = true → Updates happen automatically in the background
-    alwaysUpdate = false → Updates only happen when triggered by a peer connection
-   */
-  bool alwaysUpdate = 2;
+  // Version directive: "latest", "disabled", or a concrete semver (e.g., "0.50.1").
+  // Clients SHOULD treat unknown strings as "disabled" and MUST NOT auto-update.
+  string version = 1;
+  // Update policy:
+  //  - true  → background auto-update (periodic checks without user action)
+  //  - false → update only on management login/sync handshake
+  // Presence note: omit the entire AutoUpdateSettings message to inherit server defaults.
+  bool alwaysUpdate = 2;
 }
client/internal/updatemanager/update_freebsd.go (1)

7-10: FreeBSD update implementation pending.

The triggerUpdate method is currently a stub. According to PR comments, platform-specific installer logic will handle update execution. Ensure this TODO is tracked.

Would you like me to open an issue to track the FreeBSD implementation?

client/internal/connect.go (1)

283-285: Consider defensive nil check for PeerConfig.

The code checks if AutoUpdate is non-nil, but loginResp.PeerConfig itself could potentially be nil. Consider adding a nil check for PeerConfig before accessing its AutoUpdate field to prevent potential panic.

Apply this diff to add defensive nil check:

-	if loginResp.PeerConfig != nil && loginResp.PeerConfig.AutoUpdate != nil {
-		c.engine.InitialUpdateHandling(loginResp.PeerConfig.AutoUpdate)
-	}
+	if peerConfig := loginResp.GetPeerConfig(); peerConfig != nil && peerConfig.GetAutoUpdate() != nil {
+		c.engine.InitialUpdateHandling(peerConfig.GetAutoUpdate())
+	}
client/internal/updatemanager/update_linux.go (1)

7-10: Linux update implementation pending.

The triggerUpdate method is a stub awaiting implementation. Based on PR discussion, platform-specific installers (e.g., apt) will handle the actual update mechanics.

Would you like me to open an issue to track the Linux implementation alongside FreeBSD?

management/server/grpcserver.go (1)

774-776: AutoUpdate configuration wired correctly.

The AutoUpdate field is properly populated from account settings. Note that a non-nil AutoUpdateSettings struct is always created even when AutoUpdateVersion is an empty string.

If you want to distinguish between "no auto-update configured" vs "update to latest", consider conditionally creating the AutoUpdate struct:

+	var autoUpdate *proto.AutoUpdateSettings
+	if settings.AutoUpdateVersion != "" {
+		autoUpdate = &proto.AutoUpdateSettings{
+			Version: settings.AutoUpdateVersion,
+		}
+	}
 	return &proto.PeerConfig{
 		Address:                         fmt.Sprintf("%s/%d", peer.IP.String(), netmask),
 		SshConfig:                       &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
 		Fqdn:                            fqdn,
 		RoutingPeerDnsResolutionEnabled: settings.RoutingPeerDNSResolutionEnabled,
 		LazyConnectionEnabled:           settings.LazyConnectionEnabled,
-		AutoUpdate: &proto.AutoUpdateSettings{
-			Version: settings.AutoUpdateVersion,
-		},
+		AutoUpdate: autoUpdate,
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c282756 and 7656b38.

⛔ Files ignored due to path filters (1)
  • shared/management/proto/management.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • client/internal/connect.go (1 hunks)
  • client/internal/engine.go (6 hunks)
  • client/internal/updatemanager/manager.go (1 hunks)
  • client/internal/updatemanager/manager_test.go (1 hunks)
  • client/internal/updatemanager/update_darwin.go (1 hunks)
  • client/internal/updatemanager/update_freebsd.go (1 hunks)
  • client/internal/updatemanager/update_js.go (1 hunks)
  • client/internal/updatemanager/update_linux.go (1 hunks)
  • client/internal/updatemanager/update_windows.go (1 hunks)
  • client/ui/client_ui.go (9 hunks)
  • management/internals/server/server.go (1 hunks)
  • management/server/account.go (3 hunks)
  • management/server/activity/codes.go (2 hunks)
  • management/server/grpcserver.go (1 hunks)
  • management/server/http/handlers/accounts/accounts_handler.go (6 hunks)
  • management/server/http/handlers/accounts/accounts_handler_test.go (5 hunks)
  • management/server/peer.go (1 hunks)
  • management/server/types/settings.go (2 hunks)
  • shared/management/http/api/openapi.yml (1 hunks)
  • shared/management/http/api/types.gen.go (1 hunks)
  • shared/management/proto/management.proto (1 hunks)
  • version/update.go (3 hunks)
  • version/update_test.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
client/internal/connect.go (1)
shared/management/proto/management.pb.go (3)
  • PeerConfig (1637-1655)
  • PeerConfig (1670-1670)
  • PeerConfig (1685-1687)
management/internals/server/server.go (1)
version/update.go (1)
  • NewUpdateAndStart (53-58)
client/internal/updatemanager/update_freebsd.go (1)
client/internal/updatemanager/manager.go (1)
  • UpdateManager (46-64)
management/server/account.go (2)
management/server/types/settings.go (1)
  • Settings (10-58)
management/server/activity/codes.go (1)
  • AccountAutoUpdateVersionUpdated (183-183)
client/internal/updatemanager/update_darwin.go (1)
client/internal/updatemanager/manager.go (1)
  • UpdateManager (46-64)
client/internal/updatemanager/update_js.go (1)
client/internal/updatemanager/manager.go (1)
  • UpdateManager (46-64)
client/internal/updatemanager/manager_test.go (2)
client/internal/updatemanager/manager.go (3)
  • UpdateManager (46-64)
  • UpdateInterface (29-35)
  • NewUpdateManager (66-77)
client/internal/peer/status.go (1)
  • NewRecorder (222-233)
version/update_test.go (1)
version/update.go (1)
  • NewUpdateAndStart (53-58)
client/internal/updatemanager/update_windows.go (1)
client/internal/updatemanager/manager.go (1)
  • UpdateManager (46-64)
management/server/http/handlers/accounts/accounts_handler_test.go (2)
management/server/types/account.go (1)
  • AccountSettings (117-119)
shared/management/http/api/types.gen.go (1)
  • AccountSettings (293-336)
management/server/peer.go (1)
shared/management/proto/management.pb.go (3)
  • PeerConfig (1637-1655)
  • PeerConfig (1670-1670)
  • PeerConfig (1685-1687)
client/internal/engine.go (2)
client/internal/updatemanager/manager.go (2)
  • UpdateManager (46-64)
  • NewUpdateManager (66-77)
shared/management/proto/management.pb.go (12)
  • AutoUpdateSettings (1745-1753)
  • AutoUpdateSettings (1768-1768)
  • AutoUpdateSettings (1783-1785)
  • SyncResponse (388-405)
  • SyncResponse (420-420)
  • SyncResponse (435-437)
  • NetworkMap (1802-1832)
  • NetworkMap (1847-1847)
  • NetworkMap (1862-1864)
  • PeerConfig (1637-1655)
  • PeerConfig (1670-1670)
  • PeerConfig (1685-1687)
management/server/grpcserver.go (3)
shared/management/proto/management.pb.go (18)
  • AutoUpdateSettings (1745-1753)
  • AutoUpdateSettings (1768-1768)
  • AutoUpdateSettings (1783-1785)
  • NetworkMap (1802-1832)
  • NetworkMap (1847-1847)
  • NetworkMap (1862-1864)
  • Checks (3046-3052)
  • Checks (3067-3067)
  • Checks (3082-3084)
  • SyncResponse (388-405)
  • SyncResponse (420-420)
  • SyncResponse (435-437)
  • PeerConfig (1637-1655)
  • PeerConfig (1670-1670)
  • PeerConfig (1685-1687)
  • DNSConfig (2551-2560)
  • DNSConfig (2575-2575)
  • DNSConfig (2590-2592)
management/server/types/network.go (2)
  • NetworkMap (32-41)
  • Network (109-118)
management/server/types/settings.go (2)
  • Settings (10-58)
  • ExtraSettings (86-103)
client/ui/client_ui.go (2)
version/update.go (1)
  • NewUpdateAndStart (53-58)
client/proto/daemon.pb.go (3)
  • SystemEvent (3424-3435)
  • SystemEvent (3448-3448)
  • SystemEvent (3463-3465)
client/internal/updatemanager/manager.go (3)
version/version.go (1)
  • NetbirdVersion (18-20)
version/update.go (1)
  • NewUpdate (38-51)
client/proto/daemon.pb.go (3)
  • SystemEvent_INFO (94-94)
  • SystemEvent_SYSTEM (150-150)
  • SystemEvent_ERROR (96-96)
management/server/http/handlers/accounts/accounts_handler.go (4)
shared/management/http/api/types.gen.go (1)
  • PutApiAccountsAccountIdJSONRequestBody (1936-1936)
management/server/types/settings.go (2)
  • Settings (10-58)
  • ExtraSettings (86-103)
management/server/context/auth.go (1)
  • GetUserAuthFromContext (47-52)
shared/management/http/util/util.go (2)
  • WriteError (84-120)
  • WriteErrorResponse (70-80)
client/internal/updatemanager/update_linux.go (1)
client/internal/updatemanager/manager.go (1)
  • UpdateManager (46-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: Management / Benchmark (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Management / Integration (amd64, postgres)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Management / Benchmark (API) (amd64, sqlite)
  • GitHub Check: Management / Benchmark (API) (amd64, postgres)
  • GitHub Check: Management / Benchmark (amd64, postgres)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: Client / Unit
  • GitHub Check: release_ui_darwin
  • GitHub Check: Client / Unit
  • GitHub Check: release
  • GitHub Check: iOS / Build
  • GitHub Check: Android / Build
  • GitHub Check: JS / Lint
  • GitHub Check: Linux
  • GitHub Check: Windows
  • GitHub Check: Darwin
  • GitHub Check: Client / Unit
🔇 Additional comments (11)
shared/management/proto/management.proto (1)

270-272: <!-- [scratchpad_start]
[task review_proto_autoUpdate_1/1]
Verify all PeerConfig builders properly initialize autoUpdate field from server settings.

[observations]

  • toPeerConfig function defined at management/server/grpcserver.go:765-777
  • AutoUpdate field EXPLICITLY initialized at lines 774-776: AutoUpdate: &proto.AutoUpdateSettings{Version: settings.AutoUpdateVersion,}
  • toPeerConfig is called from 3 production locations:
    1. management/server/peer.go:1649 (passing settings)
    2. management/server/grpcserver.go:650 (passing settings)
    3. management/server/grpcserver.go:782 (passing settings in toSyncResponse)
    4. management/server/grpcserver.go:787 (passing settings in NetworkMap)
  • All call sites pass the required settings parameter
  • Other direct PeerConfig constructions (peer.go:1641, engine_test.go) are in error/empty scenarios or tests, not primary builders
  • Field is non-nil and populated from settings.AutoUpdateVersion

[analysis]

  • Primary concern is addressed: AutoUpdate IS populated in the main builder
  • Field uses correct source: settings.AutoUpdateVersion
  • All production code paths go through toPeerConfig which ensures population
  • No nil risks on client for normal flows
  • Code quality: Proper initialization pattern, settings-driven

[done]
✅ Verification complete: AutoUpdate is properly initialized from server settings in all production PeerConfig builders. Original concern validated.
[scratchpad_end]

AutoUpdate field is properly populated in all PeerConfig builders

Verification confirms that the toPeerConfig function (management/server/grpcserver.go:765–777) correctly initializes autoUpdate from settings.AutoUpdateVersion (lines 774–776). All three production call sites pass the required settings parameter, ensuring clients always receive the field populated. No nil surprises.

management/server/peer.go (1)

1649-1654: Verified: toPeerConfig is safe and AutoUpdate correctly populated

Confirmed both concerns:

  • toPeerConfig is a pure data mapper with no store lookups—only accessor calls on passed parameters (safe post-DeletePeer).
  • AutoUpdate.Version is correctly derived from settings.AutoUpdateVersion.

The change is sound.

shared/management/http/api/types.gen.go (1)

294-296: Now let me search the codebase for the constants and any defaulting logic:

Based on my verification of the code, I can now generate the rewritten review comment:


Ensure empty AutoUpdateVersion values are handled correctly per design intent

Server-side validation is already implemented at management/server/http/handlers/accounts/accounts_handler.go lines 210-219. The handler validates that non-empty values match either "latest", "disabled", or a valid semantic version.

However, empty strings currently pass through without error and are not defaulted to "latest" (line 216 condition allows empty values). Confirm whether this is intentional: if empty values should default to "latest", add that logic at line 216. The codegen file remains untouched as required.

shared/management/http/api/openapi.yml (1)

148-152: The suggested approach won't produce the expected result; oapi-codegen v1.11.1 does not convert OpenAPI pattern constraints to Go validation struct tags.

The review comment assumes that adding a pattern to the OpenAPI schema and regenerating types will produce validation tags in the generated Go code. However, oapi-codegen v1.11.1 does not automatically convert OpenAPI pattern constraints into Go validation struct tags.

What's correct: Adding a pattern to openapi.yml for auto_update_version is beneficial for API schema documentation and API-level validation. The proposed regex and description improvements are sound.

What's incorrect: The verification premise—that running codegen will add validation constraints to types.gen.go—won't happen. The pattern will remain an OpenAPI schema artifact and will not appear in the generated Go struct tags.

To actually add Go-level validation, you'd need one of these approaches:

  • Use x-oapi-codegen-extra-tags in the OpenAPI schema to inject validation tags directly
  • Manual validation in handlers or middleware
  • Use a different code generator with validation support

The review comment conflates OpenAPI schema constraints with Go struct validation, which are separate concerns in oapi-codegen.

Likely an incorrect or invalid review comment.

management/server/types/settings.go (2)

56-57: LGTM! AutoUpdateVersion field added correctly.

The new field is properly defined with an appropriate default value. The gorm tag default:'latest' provides sensible default behavior for auto-update configuration.


78-78: Copy method properly updated.

The AutoUpdateVersion field is correctly included in the Copy method, maintaining consistency with the struct definition.

management/server/activity/codes.go (2)

183-183: Activity constant added correctly.

The new AccountAutoUpdateVersionUpdated activity follows the established pattern and uses the next sequential value (91).


295-295: Activity mapping entry is correct.

The mapping entry follows the naming convention and provides clear, descriptive text for the auto-update version change activity.

management/internals/server/server.go (1)

186-186: Update initialization correctly refactored.

The change from NewUpdate to NewUpdateAndStart aligns with the new API that automatically starts the version fetcher goroutine. The existing StopWatch() call in the Stop() method (line 216) will properly clean up the started goroutine.

version/update_test.go (1)

26-26: Tests correctly updated for new API.

All test functions have been updated to use NewUpdateAndStart instead of NewUpdate, aligning with the refactored initialization pattern. The existing StopWatch() cleanup calls ensure proper goroutine termination.

Also applies to: 51-51, 76-76

management/server/grpcserver.go (1)

784-787: NetworkMap correctly includes PeerConfig.

The PeerConfig field is properly added to the NetworkMap proto response, enabling clients to receive auto-update configuration alongside network topology. The spacing adjustments improve readability.

Comment on lines +44 to +50
err = cmd.Start()
if err != nil {
return fmt.Errorf("error running pkg file: %w", err)
}
err = cmd.Process.Release()

return err
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wait for the pkg installer to finish before returning.

We start the installer and immediately Release the process, so triggerUpdate reports success even if the install fails (or is killed by the 1‑minute deadline). We also lose the installer’s exit status entirely. Please run the command synchronously so we only return after the pkg has completed (and propagate any failure).

Apply this diff:

-	err = cmd.Start()
-	if err != nil {
-		return fmt.Errorf("error running pkg file: %w", err)
-	}
-	err = cmd.Process.Release()
-
-	return err
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("error running pkg file: %w", err)
+	}
+	return nil
🤖 Prompt for AI Agents
In client/internal/updatemanager/update_darwin.go around lines 44 to 50, the
code currently starts the pkg installer and immediately calls Process.Release
which returns before the installer exits and drops its exit status; replace this
with a synchronous run (either use cmd.Run() or call cmd.Start() followed by
cmd.Wait()) so the function only returns after the installer completes, capture
and return any error from Run/Wait (wrapping with fmt.Errorf("%w") if needed),
and remove the immediate Process.Release so the installer's exit status is
propagated.

Comment on lines +56 to +85
fileInfo, err := os.Stat("/opt/homebrew/Library/Taps/netbirdio/homebrew-tap/")
if err != nil {
return fmt.Errorf("error getting homebrew installation path info: %w", err)
}

fileSysInfo, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("error checking file owner, sysInfo type is %T not *syscall.Stat_t", fileInfo.Sys())
}

// Get username from UID
installer, err := user.LookupId(fmt.Sprintf("%d", fileSysInfo.Uid))
if err != nil {
return fmt.Errorf("error looking up brew installer user: %w", err)
}
userName := installer.Name
// Get user HOME, required for brew to run correctly
// https://github.com/Homebrew/brew/issues/15833
homeDir := installer.HomeDir
// Homebrew does not support installing specific versions
// Thus it will always update to latest and ignore targetVersion
upgradeArgs := []string{"-u", userName, "/opt/homebrew/bin/brew", "upgrade", "netbirdio/tap/netbird"}
// Check if netbird-ui is installed
cmd := exec.CommandContext(ctx, "brew", "info", "--json", "netbirdio/tap/netbird-ui")
err = cmd.Run()
if err == nil {
// netbird-ui is installed
upgradeArgs = append(upgradeArgs, "netbirdio/tap/netbird-ui")
}
cmd = exec.CommandContext(ctx, "sudo", upgradeArgs...)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle Homebrew installations outside /opt/homebrew.

The tap path and brew executable are hard-coded to /opt/homebrew. On Intel macOS the default prefix is /usr/local/Homebrew (brew lives in /usr/local/bin), so os.Stat fails and the upgrade always returns the error path—auto-update will never run there. We should detect the actual Homebrew prefix (e.g. sudo -u <user> brew --prefix) and derive both the tap directory and brew binary from it, with a sane error if no prefix is found.

One possible fix:

-	fileInfo, err := os.Stat("/opt/homebrew/Library/Taps/netbirdio/homebrew-tap/")
-	if err != nil {
-		return fmt.Errorf("error getting homebrew installation path info: %w", err)
-	}
+	brewPrefixCmd := exec.CommandContext(ctx, "sudo", "-u", userName, "brew", "--prefix")
+	brewPrefixCmd.Env = append(os.Environ(), "HOME="+homeDir)
+	prefixBytes, err := brewPrefixCmd.Output()
+	if err != nil {
+		return fmt.Errorf("error determining homebrew prefix: %w", err)
+	}
+	brewPrefix := strings.TrimSpace(string(prefixBytes))
+	tapDir := filepath.Join(brewPrefix, "Library", "Taps", "netbirdio", "homebrew-tap")
+
+	fileInfo, err := os.Stat(tapDir)
+	if err != nil {
+		return fmt.Errorf("error getting homebrew installation path info: %w", err)
+	}
@@
-	upgradeArgs := []string{"-u", userName, "/opt/homebrew/bin/brew", "upgrade", "netbirdio/tap/netbird"}
+	brewBin := filepath.Join(brewPrefix, "bin", "brew")
+	upgradeArgs := []string{"-u", userName, brewBin, "upgrade", "netbirdio/tap/netbird"}

Remember to add the necessary imports (filepath).
Based on learnings

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In client/internal/updatemanager/update_darwin.go around lines 56 to 85, the
code currently hard-codes /opt/homebrew for the tap path and brew binary which
fails on Intel macs; change it to detect the actual Homebrew prefix for the
installer user (run sudo -u <user> brew --prefix via exec.CommandContext,
capture and trim stdout) and derive the tap directory as filepath.Join(prefix,
"Library", "Taps", "netbirdio", "homebrew-tap") and the brew binary as
filepath.Join(prefix, "bin", "brew"); if the prefix command fails or returns
empty, return a clear error; update subsequent os.Stat and brew invocations to
use these derived paths (use exec.CommandContext(ctx, "sudo", "-u", userName,
derivedBrewPath, "info", "--json", "netbirdio/tap/netbird-ui") and similarly for
upgrade), and add the filepath import. Ensure errors are wrapped with context
and fall back only to a clear failure rather than assuming /opt/homebrew.

Comment on lines +209 to +218
}
if req.Settings.AutoUpdateVersion != nil {
_, err := goversion.NewSemver(*req.Settings.AutoUpdateVersion)
if *req.Settings.AutoUpdateVersion == autoUpdateLatestVersion ||
*req.Settings.AutoUpdateVersion == disableAutoUpdate ||
err == nil {
returnSettings.AutoUpdateVersion = *req.Settings.AutoUpdateVersion
} else if *req.Settings.AutoUpdateVersion != "" {
return nil, fmt.Errorf("invalid AutoUpdateVersion")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return a status.InvalidArgument error for invalid versions.
Returning fmt.Errorf makes util.WriteError emit a 500 “internal server error” to clients who send a bad version. We should surface a 422 InvalidArgument instead.

-			return nil, fmt.Errorf("invalid AutoUpdateVersion")
+			return nil, status.Errorf(status.InvalidArgument, "invalid auto-update version")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}
if req.Settings.AutoUpdateVersion != nil {
_, err := goversion.NewSemver(*req.Settings.AutoUpdateVersion)
if *req.Settings.AutoUpdateVersion == autoUpdateLatestVersion ||
*req.Settings.AutoUpdateVersion == disableAutoUpdate ||
err == nil {
returnSettings.AutoUpdateVersion = *req.Settings.AutoUpdateVersion
} else if *req.Settings.AutoUpdateVersion != "" {
return nil, fmt.Errorf("invalid AutoUpdateVersion")
}
}
if req.Settings.AutoUpdateVersion != nil {
_, err := goversion.NewSemver(*req.Settings.AutoUpdateVersion)
if *req.Settings.AutoUpdateVersion == autoUpdateLatestVersion ||
*req.Settings.AutoUpdateVersion == disableAutoUpdate ||
err == nil {
returnSettings.AutoUpdateVersion = *req.Settings.AutoUpdateVersion
} else if *req.Settings.AutoUpdateVersion != "" {
return nil, status.Errorf(status.InvalidArgument, "invalid auto-update version")
}
🤖 Prompt for AI Agents
management/server/http/handlers/accounts/accounts_handler.go around lines 209 to
218: the code currently returns fmt.Errorf("invalid AutoUpdateVersion") for bad
versions which causes util.WriteError to emit a 500; change the error return to
a gRPC InvalidArgument error so clients receive a 422/invalid-argument. Replace
the fmt.Errorf(...) with a status.Errorf(codes.InvalidArgument, "invalid
AutoUpdateVersion: %s", *req.Settings.AutoUpdateVersion) (and add/import
"google.golang.org/grpc/status" and "google.golang.org/grpc/codes" if not
already present).

Comment thread management/server/http/handlers/accounts/accounts_handler.go
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
10 New issues
10 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@pappz pappz closed this Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.